package com.fuyao.cloud.admin.flow.controller;

import cn.hutool.core.collection.CollUtil;
import com.fuyao.cloud.admin.api.entity.SysUser;
import com.fuyao.cloud.admin.flow.constant.FlowConstant;
import com.fuyao.cloud.admin.flow.dto.ActivityVo;
import com.fuyao.cloud.admin.flow.enm.NodeStatusEnum;
import com.fuyao.cloud.admin.flow.enm.NodeTypeEnum;
import com.fuyao.cloud.admin.flow.model.ExtendHisprocinst;
import com.fuyao.cloud.admin.flow.service.flowable.ActorUserService;
import com.fuyao.cloud.admin.flow.service.flowable.IExtendHisprocinstService;
import com.fuyao.cloud.admin.flow.util.DurationUtils;
import com.fuyao.cloud.admin.flow.util.FlowableHelper;
import com.fuyao.cloud.admin.service.SysUserService;
import com.fuyao.cloud.common.core.util.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.identitylink.api.IdentityLinkInfo;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequiredArgsConstructor
@Api(value = "flowable", tags = "节点信息获取")
@RequestMapping("/flowable/node")
public class FlowableNodeController {
    private final RepositoryService repositoryService;
    private final HistoryService historyService;
    private final SysUserService sysUserService;
    private final ActorUserService actorUserService;
    private final IExtendHisprocinstService extendHisprocinstService;

    @ApiOperation(value = "查询节点信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "businessKey", required = true, value = "流程实例id", defaultValue = "018273249827498324", dataTypeClass = String.class, paramType = "path"),
            @ApiImplicitParam(name = "nodeId", required = true, value = "节点id", defaultValue = "12143334", dataTypeClass = String.class, paramType = "path")
    })
    @GetMapping(value = "/getNodeInfo/{businessKey}/{nodeId}")
    public R<ActivityVo> getNodeInfo(@PathVariable("businessKey") String businessKey, @PathVariable("nodeId") String nodeId) {
        return R.ok(getNodeDetail(businessKey, nodeId));
    }

    public ActivityVo getNodeDetail(String businessKey, String nodeId) {
        if (StringUtils.isBlank(businessKey) || StringUtils.isBlank(nodeId)) {
            return null;
        }

        ExtendHisprocinst extendHisprocinst = extendHisprocinstService.findExtendHisprocinstByBusinessKey(businessKey);

        BpmnModel bpmnModel = repositoryService.getBpmnModel(extendHisprocinst.getProcessDefinitionId());
        FlowElement flowElement = bpmnModel.getFlowElement(nodeId);

        if (!(flowElement instanceof UserTask)) {
            return null;
        }

        List<HistoricTaskInstance> historicTaskInstances = historyService
                .createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .taskDefinitionKey(nodeId)
                .includeIdentityLinks()
                .orderByTaskCreateTime()
                .desc()
                .list();

        boolean isFinished = false;
        boolean hasHistoricTaskInstances = false;

        if (CollUtil.isNotEmpty(historicTaskInstances)) {
            hasHistoricTaskInstances = true;
            isFinished = historicTaskInstances.stream().allMatch(historicTaskInstance -> historicTaskInstance.getEndTime() != null);
        }

        UserTask userTask = (UserTask) flowElement;

        // 没有待办或没有经过的环节
        if (!hasHistoricTaskInstances || !this.checkUserTaskExist(extendHisprocinst.getProcessInstanceId(), bpmnModel, userTask)) {
            ActivityVo activityVo = this.createNodeInfo(userTask, extendHisprocinst);
            activityVo.setHandlers(FlowableHelper
                    .getNodeHandlers(sysUserService, actorUserService, extendHisprocinst.getProcessInstanceId(), userTask)
                    .stream()
                    .map(handler -> ActivityVo.Handler.builder()
                            .status(ActivityVo.Handler.NONE)
                            .id(Long.valueOf(handler.getId()))
                            .username(handler.getCode())
                            .name(handler.getName())
                            .build()
                    ).collect(Collectors.toSet())
            );
            activityVo.setStatus(NodeStatusEnum.PENDING.getDescription());
            return activityVo;
        }

        // 处理中的环节
        if (!isFinished) {
            ActivityVo activityVo = this.createNodeInfo(userTask, extendHisprocinst);
            activityVo.setStartDate(historicTaskInstances.stream().map(TaskInfo::getCreateTime).min(Date::compareTo).orElse(null));
            activityVo.setStatus(NodeStatusEnum.PROCESSING.getDescription());
            activityVo.setHandlers(getHandlers(historicTaskInstances));
            return activityVo;
        }

        // 已完成的环节
        return this.getDoneNodeInfo(historicTaskInstances, userTask, extendHisprocinst);

    }

    private ActivityVo getDoneNodeInfo(List<HistoricTaskInstance> historicTaskInstances, UserTask userTask, ExtendHisprocinst extendHisprocinst) {
        ActivityVo activityVo = this.createNodeInfo(userTask, extendHisprocinst);
        activityVo.setStatus(NodeStatusEnum.FINISH.getDescription());
        List<Date> createTimes = new ArrayList<>();
        List<Date> endTimes = new ArrayList<>();
        historicTaskInstances.forEach(taskInstance -> {
            createTimes.add(taskInstance.getCreateTime());
            endTimes.add(taskInstance.getEndTime());
        });
        if (CollUtil.isNotEmpty(createTimes)) {
            activityVo.setStartDate(Collections.min(createTimes));
        }
        if (CollUtil.isNotEmpty(endTimes)) {
            activityVo.setEndDate(Collections.max(endTimes));
        }

        activityVo.setHandlers(getHandlers(historicTaskInstances));

        long duration = historicTaskInstances.stream()
                .map(taskInstance -> Optional.of(taskInstance.getDurationInMillis()).orElse(0L))
                .reduce(Long::sum).orElse(0L);

        activityVo.setDuration(DurationUtils.getDuration(duration));
        return activityVo;
    }

    private ActivityVo createNodeInfo(UserTask userTask, ExtendHisprocinst extendHisprocinst) {
        ActivityVo activityVo = new ActivityVo();
        activityVo.setId(userTask.getId());
        activityVo.setDocumentation(userTask.getDocumentation());
        activityVo.setName(userTask.getName());
        activityVo.setProceInsId(extendHisprocinst.getProcessInstanceId());
        activityVo.setProceDefId(extendHisprocinst.getProcessDefinitionId());
        activityVo.setTaskDefKey(userTask.getId());

        String taskType = FlowableHelper.getSingleCustomProperty(userTask, FlowConstant.NODE_TYPE);
        String description = NodeTypeEnum.TYPES.get(taskType);
        activityVo.setNodeType(StringUtils.isNotBlank(description) ? description : NodeTypeEnum.APPLY.getDescription());

        return activityVo;
    }

    private Set<ActivityVo.Handler> getHandlers(List<HistoricTaskInstance> historicTaskInstances) {
        List<String> usernames = new ArrayList<>();

        Map<String, String> finishMap = new HashMap<>();

        Set<ActivityVo.Handler> handlers = new HashSet<>();
        historicTaskInstances.forEach(historicTaskInstance -> {
            List<? extends IdentityLinkInfo> identityLinks = historicTaskInstance.getIdentityLinks();
            String assignee = historicTaskInstance.getAssignee();
            Set<String> names = new HashSet<>();

            if (assignee != null) {
                names.add(assignee);
            } else { // 单任务实例
                if (!identityLinks.isEmpty()) {
                    names = identityLinks.stream().map(IdentityLinkInfo::getUserId).collect(Collectors.toSet());
                }
            }
            names.forEach(username -> {
                if (!ActivityVo.Handler.DOING.equals(finishMap.get(username))) {
                    finishMap.put(username, historicTaskInstance.getEndTime() != null ? ActivityVo.Handler.DONE : ActivityVo.Handler.DOING);
                }
                if (!usernames.contains(username)) {
                    handlers.add(ActivityVo.Handler
                            .builder()
                            .username(username)
                            .build());
                    usernames.add(username);
                }
            });
        });
        if (CollUtil.isNotEmpty(usernames)) {
            List<SysUser> sysUsers = sysUserService.findUsesByUserNames(usernames);
            Map<String, SysUser> sysUserMap = FlowableHelper.toMap(sysUsers, SysUser::getUsername);
            handlers.forEach(handler -> {
                SysUser sysUser = sysUserMap.get(handler.getUsername());
                handler.setStatus(finishMap.get(handler.getUsername()));
                if (sysUser != null) {
                    handler.setName(sysUser.getName());
                    handler.setId(sysUser.getUserId());
                }
            });
        }
        return handlers;
    }

    /**
     * 判断 UserTask 是不是流程模型中的节点
     *
     * @param processInstanceId 流程实例 id
     * @param bpmnModel         流程模型
     * @param userTask          用户任务节点
     * @return true 是
     */
    private boolean checkUserTaskExist(String processInstanceId, BpmnModel bpmnModel, UserTask userTask) {
        List<HistoricActivityInstance> historicSequenceFlows = FlowableHelper.getHistoricSequenceFlowActivityInstances(historyService,
                processInstanceId, BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW);

        List<String> sequenceFlowIds = historicSequenceFlows
                .stream()
                .map(HistoricActivityInstance::getActivityId)
                .collect(Collectors.toList());

        List<SequenceFlow> flowElements = FlowableHelper.getFlowElements(bpmnModel, sequenceFlowIds);

        for (SequenceFlow sequenceFlow : flowElements) {
            FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
            FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
            if (userTask.getId().equals(sourceFlowElement.getId()) || userTask.getId().equals(targetFlowElement.getId())) {
                return true;
            }
        }
        return false;
    }

}
